ffmpeg 视频解码h264和yuv

之前学习 ffmpegandroid 平台上,发现很不方便,所以打算在 vs 上重新搭建环境,然后重新学习,之后如果需要用到的话在移植到其他平台。环境搭建参考的是: https://blog.csdn.net/weixinhum/article/details/37699025

环境

Microsoft Visual C++ 2017
vs2017
ffmpeg 3.4.2

步骤主要是以下几大步骤:

  1. 初始化
1
2
av_register_all();
avformat_network_init();
  1. AVFormatContext获取和初始化
1
2
3
4
5
6
//AVFormatContext获取
avformat_alloc_context()
//打开文件,和AVFormatContext关联
avformat_open_input()
//获取文件流信息
avformat_find_stream_info()
  1. 获取解码器
1
2
3
4
5
6
7
8
//AVCodecContext获取
avcodec_alloc_context3()
//将AVCodecParameters转换为AVCodecContext
avcodec_parameters_to_context()
//获取解码器
avcodec_find_decoder()
//打开解码器
avcodec_open2()
  1. 解码准备
1
2
3
4
5
6
7
8
9
10
//获取解码数据包装 AVFrame
av_frame_alloc()
//根据宽高,解码类型(yuv420)获取缓存buffer大小
av_image_get_buffer_size()
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的AVFrame里面
av_image_fill_arrays()
//获取编码数据 包装 AVPacket
av_packet_alloc()
//获取SwsContext 图片转换(宽高这些)需要用到
sws_getContext()
  1. 读取数据源解码存储
1
2
3
4
5
6
7
8
9
10
//读取编码数据源到AVPacket
av_read_frame()
//发送数据源
avcodec_send_packet()
//解码数据源 ,和avcodec_send_packet配合使用
avcodec_receive_frame()
//图像转换
sws_scale()
//写入文件
fwrite()
  1. 回收

具体代码和步骤如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include <iostream>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

//初始化
void init() {
av_register_all();
avformat_network_init();
}

void log(const char * msg, int d=-1123) {
if (d == -1123) {
printf_s("%s\n", msg);
}
else {
printf_s("%s %d \n", msg ,d);
}

}


int video2YuvAndH264(const char * filePath,FILE * yuvFilePath, FILE * h264FilePath){
AVFormatContext * pFmtCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVFrame *pFrame = NULL;
AVFrame *pFrameYUV = NULL;
uint8_t *outBuffer = NULL;
AVPacket *pPacket = NULL;
SwsContext *pSwsCtx = NULL;

//1. 初始化
init();
//2. AVFormatContext获取
pFmtCtx = avformat_alloc_context();
//3. 打开文件
if (avformat_open_input(&pFmtCtx,filePath,NULL,NULL)!=0) {
log("Couldn't open input stream.\n");
return -1;
}
//4. 获取文件信息
if (avformat_find_stream_info(pFmtCtx,NULL)<0) {
log("Couldn't find stream information.");
return -1;
}
//5. 获取视频的index
int i = 0,videoIndex =-1;
for (; i < pFmtCtx->nb_streams; i++) {
if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}

if (videoIndex == -1) {
log("Didn't find a video stream.");
return -1;
}
//6. 获取解码器并打开
pCodecCtx = avcodec_alloc_context3(NULL);
if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
log("Didn't parameters to contex.");
return -1;
}
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
log("Codec not found.");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
log("Could not open codec.");
return -1;
}
//7. 解码开始准备工作
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();

//根据需要解码的类型,获取需要的buffer,不要忘记free
outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)*sizeof(uint8_t));
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);

pPacket = av_packet_alloc();
log("--------------- File Information ----------------");
av_dump_format(pFmtCtx, 0, filePath, 0);
log("-------------------------------------------------");
//获取SwsContext
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);

int count=0;
//8. 读取数据
while (av_read_frame(pFmtCtx, pPacket) == 0) {//读取一帧压缩数据
if (pPacket->stream_index == videoIndex) {
//写入H264数据到文件
fwrite(pPacket->data, 1, pPacket->size, h264FilePath); //把H264数据写入h264FilePath文件
//解码数据
//avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pPacket);
if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
log("Decode end or Error.\n");
break;
}else {//处理解码数据并写入文件
avcodec_receive_frame(pCodecCtx,pFrame);

if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize) == 0) {
continue;
}

count++;
int y_size = pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, y_size, yuvFilePath); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, yuvFilePath); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, yuvFilePath); //V
log("Succeed to decode frame!",count);
}
}
av_packet_unref(pPacket);
}
//flush decoder
/*当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。
因此需要通过“flush_decoder”将这几帧数据输出。
“flush_decoder”功能简而言之即直接调用avcodec_decode_video2()获得AVFrame,而不再向解码器传递AVPacket。*/
while (1) {
if (avcodec_send_packet(pCodecCtx, pPacket)!= 0) {
log("Decode end or Error.\n");
break;
}
else {
avcodec_receive_frame(pCodecCtx, pFrame);

sws_scale(pSwsCtx, (const unsigned char *const *)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);

int y_size = pCodecCtx->width * pCodecCtx->height;
// yuv-> 4:1:1
fwrite(pFrameYUV->data[0], 1, static_cast<size_t>(y_size), yuvFilePath); //Y
fwrite(pFrameYUV->data[1], 1, static_cast<size_t>(y_size / 4), yuvFilePath); //U
fwrite(pFrameYUV->data[2], 1, static_cast<size_t>(y_size / 4), yuvFilePath); //V
log("Flush Decoder: Succeed to decode frame!", count);
}
}

if (pSwsCtx != NULL) {
sws_freeContext(pSwsCtx);
}
if (outBuffer != NULL) {
av_free(outBuffer);
}
if (pFrameYUV != NULL) {
av_frame_free(&pFrameYUV);
}
if (pFrame != NULL) {
av_frame_free(&pFrame);
}
if (pCodecCtx != NULL) {
avcodec_close(pCodecCtx);
}
if (pFmtCtx != NULL) {
avformat_close_input(&pFmtCtx);
}

}


int main()
{
FILE * yuvFile;
FILE * h264File;
fopen_s(& yuvFile,"F:/视频资源/gxsp.yuv", "wb+");
fopen_s(& h264File,"F:/视频资源/gxsp.h264", "wb+");
video2YuvAndH264("F:/视频资源/gxsp.mp4", yuvFile, h264File);

fclose(yuvFile);
fclose(h264File);

getchar();

return 0;
}

在Android上进行视频解码

参考链接: https://blog.csdn.net/king1425/article/details/71160339

-------------The End-------------